package general;

import java.util.ListIterator;
import java.util.Random;
import java.util.Vector;
import java.util.Collections;

public class Device {

    private int m; //granularity, m x m bits per cell
    private int dim; //dimension, dim x dim cells per functional unit
    protected Cell[][] cellfield; //field of cells in the functional unit
    protected Vector<Functional_Unit> modules;
    protected int num_rows;
    public int failures;

    public static final int default_granularity = 4;

    //
    //default constructor
    //
    public Device () {
        modules = new Vector<Functional_Unit>();

        setM(default_granularity);
        setDim(25);
        num_rows = dim;
        
        createCellfield();
    }

    //
    //regular constructor
    //
    protected Device (int total_bits, int granularity) {
        modules = new Vector<Functional_Unit>();

        setM(granularity);
        setDim(total_bits / granularity);
        num_rows = dim;
        
        createCellfield();
    }

    //
    //create the dim x dim field of cells
    //
    protected void createCellfield() {
        //create the field of cells
        this.cellfield = new Cell[num_rows][dim];

        //initialize the cell field
        for(int i = 0; i < num_rows; i++) {
            for(int j = 0; j < dim; j++) {
                this.cellfield[i][j] = new Cell();
            }
        }
    }

    //
    //return the value of m
    //
    public int getM () {
        return m;
    }

    //
    //set the value of m
    //
    public void setM (int val) {
        this.m = val;
    }

    //
    //get the value of dim
    //
    public int getDim () {
        return dim;
    }

    //
    //set the value of dim
    //
    public void setDim (int val) {
        this.dim = val;
    }

    //
    //increment the value of dim by one
    //
    protected void incDim() {
        this.dim++;
    }
    
    //
    //get the value of num_rows
    //
    public int getNumRows() {
        return num_rows;
    }
    
    //
    //set the value of num_rows
    //
    public void setNumRows(int val) {
        this.num_rows = val;
    }

    //
    //initialize the values in the cells
    //
    public void initialize(int inA, int inB) {
        int i, j;

        //split the inputs into m-bit chunks
        Val_Array opA = new Val_Array(inA);
        Val_Array opB = new Val_Array(inB);
        Val_Array opC = new Val_Array(0);
        Val_Array opD = new Val_Array(0);

        //assign the chunks to their corresponding cells
        for(i = 0; i < num_rows; i++) {
            for(j = 0; j < dim; j++) {
                //set cell values
                cellfield[i][j].setOperands(opA.getValArray()[i], //start from the left
                    opB.getValArray()[dim - 1 - j], //start from the right
                    0, 0); //no carries
            }
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    //  The following methods are unique to the device level. They should not
    //  be used by inheriting classes.
    ///////////////////////////////////////////////////////////////////////////

        
    //
    //Format the device, returning all cells to their default type
    //Note: faulty cells are preserved
    //
    public void format() {
        //iterate over all the rows and columns
        for(int i = 0; i < this.num_rows; i++) {
            for(int j = 0; j < this.dim; j++) {
                //if a cell is not faulty
                if(cellfield[i][j].getType() != 'O') {
                    //return it to its default type
                    cellfield[i][j].setType('X');
                }
            }
        }
    }
    
    
    //
    //Format the device, all cells become of type X
    //Note: faulty cells are not preserved
    //
    public void replace() {
        //iterate over all the rows and columns
        for(int i = 0; i < this.num_rows; i++) {
            for(int j = 0; j < this.dim; j++) {
                //return it to its default type
                cellfield[i][j].setType('X');
            }
        }        
    }
    
    
    //
    //inject howMany faults into the device
    //
    public void injectFaults(int howMany) {
        int x, y, faultsPlaced = 0;
        Random generator = new Random();

        while(faultsPlaced < howMany) {
            //randomly select the coordinates of the fault
            x = Math.abs(generator.nextInt() % num_rows);
            y = Math.abs(generator.nextInt() % dim);

           //place the faults
            cellfield[x][y].setType('O');

            faultsPlaced++;
        }
    }

    //
    //add a functional unit to the device
    //
    public void addModule(Functional_Unit fu) {
        modules.add(fu);
    }


    //
    //places all of the modules that have been added to the device
    //
    public boolean placeModules(int option) {
        Vector<Functional_Unit> sorted_modules = new Vector<Functional_Unit>();
        ListIterator module_iterator;
        FUComparator comp = new FUComparator();
        
        failures = 0;
        
        module_iterator = modules.listIterator();
        while( module_iterator.hasNext()) {
            sorted_modules.add((Functional_Unit)module_iterator.next());
        }
        Collections.sort(sorted_modules, comp);
        
        switch(option) {
            case 1: //size-aware fault avoidance with fu rotation
                module_iterator = sorted_modules.listIterator();
                
                //place the fu's one by one
                while( module_iterator.hasNext() ) {
                    if( ! smartPlace((Functional_Unit)module_iterator.next())) {
                        return false;
                    }
                }
                break;                
            case 2: //size-aware fault avoidance                
                module_iterator = sorted_modules.listIterator();
                
                //place the fu's one by one
                while( module_iterator.hasNext() ) {
                    if( ! improvedPlace((Functional_Unit)module_iterator.next())) {
                        return false;
                    }
                }            
                break;
            case 3: //simple fault avoidance                
                module_iterator = modules.listIterator();
                
                //place the fu's one by one
                while(module_iterator.hasNext()) {
                    if( ! improvedPlace((Functional_Unit)module_iterator.next())) {
                        return false;
                    }
                }
                break;
            case 4: //no fault avoidance
            default:
                module_iterator = modules.listIterator();
                
                //if we catch an exception (meaning that a fault was encountered)
                //or if the fu cannot be mapped
                while(module_iterator.hasNext()) {
                    if( ! baselinePlace((Functional_Unit)module_iterator.next())) {
                        return false;
                    }
                }
                break;
        }
        return true;
    }
    
    private boolean smartPlace(Functional_Unit fu) {
        boolean success = false;
        
        if(!(success = improvedPlace(fu))) {
            success = rotatedPlace(fu);
        }
        
        return success;
    }
    
    //
    //place a functional unit on the device, avoiding faults and other fu's
    //Note: called simplePlace because of brute force (not fancy) algorithm
    //
    private boolean improvedPlace(Functional_Unit fu) {
        //find a vacant spot on the device, big enough for this functional unit
        //iterate over the columns and rows of the device
        
        //try to place the fu
        for(int origin_row = 0; origin_row <= this.num_rows - fu.getDim(); origin_row++) {
            for(int origin_col = 0; origin_col <= this.dim - fu.getDim(); origin_col++) {
                //see if there's room for placement
                //if so, write the fu to the device
                if( attemptPlacement(origin_row, origin_col, fu) ) {
                    //placement successful
                    return true;
                }
                
            }
        }
        
        return false;
    }
    
    //
    //place unite on the device, throwing an exception if a faulty cell is encountered
    //
    private boolean baselinePlace(Functional_Unit fu) {
        int success;
        
        //try to place the fu
        for(int origin_row = 0; origin_row <= this.num_rows - fu.getDim(); origin_row++) {
            for(int origin_col = 0; origin_col <= this.dim - fu.getDim(); origin_col++) {
                //see if there's room for placement
                success = attemptBaselinePlacement(origin_row, origin_col, fu);
                //if so, write the fu to the device
                if( success == 1) {
                    //placement successful
                    return true;
                }
                //or, if we encountered a fault
                else if(success == -1) {
                    //system mapping failure
                    return false;
                }
                //if success == 0, continue looking for a big enough spot
            }
        }
        
        return false;
    }
    
    
    //
    //place a functional unit on the device after rotating it 90 degrees
    //
    private boolean rotatedPlace(Functional_Unit fu) {
        
        for(int origin_row = 0; origin_row <= this.num_rows - fu.getDim(); origin_row++) {
            for(int origin_col = 0; origin_col <= this.dim - fu.getDim(); origin_col++) {
                if( attemptRotatedPlacement(origin_row, origin_col, fu)) {
                    return true;
                }
            }
        }
        
        return false;
    }
    
    //
    //attempt to place the current fu at the given location
    //
    private boolean attemptPlacement(int origin_row, int origin_col, Functional_Unit fu) {
        boolean placed = false;
        
        //see if the FU will fit here
        if(testFootprint(origin_row, origin_col, fu)) {
            //if it fits place it
            mapFU(origin_row, origin_col, fu);
            placed = true;
        }
        //either the FU won't fit, or it ran into a fault
        return placed;
    }
    
    //
    //attempt to place the current fu at the given location
    //
    private int attemptBaselinePlacement(int origin_row, int origin_col, Functional_Unit fu) {
        int placed = testBaselineFootprint(origin_row, origin_col, fu);
        
        //see if the FU will fit here
        if(placed == 1) {
            //if it fits place it
            mapFU(origin_row, origin_col, fu);
        }
        //either the FU won't fit (0), or it ran into a fault (-1)
        return placed;
    }
    
    //
    //rotate the current fu by 90 degrees and attempt to place it at the given location
    //
    private boolean attemptRotatedPlacement(int origin_row, int origin_col, Functional_Unit fu) {
        boolean placed = false;
        
        if(testRotatedFootprint(origin_row, origin_col, fu)) {
            mapRotatedFU(origin_row, origin_col, fu);
            placed = true;
        }
        
        return placed;        
    }
    
    //
    //Test the current spot on the device as an origin
    //
    private boolean testFootprint(int origin_row, int origin_col, Functional_Unit fu) {
        
        //see if there are any obstructions to prevent placement
        for(int i = 0; i < fu.getNumRows(); i++) {
            for(int j = 0; j < fu.getDim(); j++) {
                //the current cell is not free
                //(it's either faulty or in use)
                if(cellfield[origin_row + i][origin_col + j].getType() != 'X') {
                    return false;
                }
            }
        }
        
        return true;
    }
    
    //
    //Test the current spot on the device as an origin
    //
    private int testBaselineFootprint(int origin_row, int origin_col, Functional_Unit fu) {

        //see if there are any obstructions to prevent placement
        for(int i = 0; i < fu.getNumRows(); i++) {
            for(int j = 0; j < fu.getDim(); j++) {
                //the current cell is faulty
                if(cellfield[origin_row + i][origin_col + j].getType() == 'O') {
                    return -1;
                }
                //the current cell is not free
                if(cellfield[origin_row + i][origin_col + j].getType() != 'X') {
                    return 0;
                }
            }
        }
        
        return 1;
    }
    
    //
    //
    //
    private boolean testRotatedFootprint(int origin_row, int origin_col, Functional_Unit fu) {
        
        for(int j = 0; j < fu.getDim(); j++) {
            for(int i = fu.getNumRows() - 1; i >= 0; i--) {
                //System.out.println("origin_row + i: " + (origin_row + i) + "\norigin_col + j:" + (origin_col + j));
                if(cellfield[origin_row + i][origin_col + j].getType() != 'X') {
                    return false;
                }
            }        
        }
        
        return true;
    }
    
    //
    //Map the cells of the given FU to the device
    //starting at the specified origin
    //
    private void mapFU(int origin_row, int origin_col, Functional_Unit fu) {
        //if we get here the cell are all free
        //now we just copy the fu structure to the device
        for(int i = 0; i < fu.getNumRows(); i++) {
            for(int j = 0; j < fu.getDim(); j++) {
                cellfield[origin_row + i][origin_col + j].setType(fu.cellfield[i][j].getType());
            }
        }
    }
    
    //
    //Rotate the FU by 90 degrees, then map them to the device
    //starting at the specified origin
    //
    private void mapRotatedFU(int origin_row, int origin_col, Functional_Unit fu) {
        //if we get here the cell are all free
        //now we just copy the fu structure to the device
        for(int j = 0; j < fu.getDim(); j++) {
            for(int i = fu.getNumRows() - 1; i >= 0; i--) {
            //for(int i = 0; i < fu.getNumRows(); i++) {
                cellfield[origin_row + j][origin_col + fu.getNumRows() - 1 - i].setType(fu.cellfield[i][j].getType());
            }
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    //  The following methods are tools for verification. They may become
    //  obsolete in the future and can be listed as deprecated.
    ///////////////////////////////////////////////////////////////////////////

    //
    //print the cell type layout in a table
    //
    public void printTypes() {
       //iterate through the rows
       for(int i = 0; i < num_rows; i++) {
           //iterate through the columns
           for(int j = 0; j < dim; j++) {
               //print the cell type
               cellfield[i][j].printType();
               //print one whitespace character
               System.out.print(' ');
           }
           //end of the row
           System.out.print('\n');
       }
    }
    
    //
    //print the device or fu after rotating it 90 degrees
    //
    public void printTypes_Rotate() {
       //iterate through the rows
       System.out.println("dim: " + dim + " num_rows: " + num_rows);
       for(int j = 0; j < dim; j++) {
           //iterate through the columns
           for(int i = num_rows - 1; i >= 0; i--) {
               //print the cell type
               cellfield[i][j].printType();
               //print one whitespace character
               System.out.print(' ');
           }
           //end of the row
           System.out.print('\n');
       }
    }
   
    //
    //print the values held by each cell in a table
    //
    public void printValues() {
        //iterate through the rows
        for(int i = 0; i < num_rows; i++) {
            //iterate through the columns
            for(int j = 0; j < dim; j++) {
                //print the cell operands
                cellfield[i][j].printOperands();
                //print one whitespace character
                System.out.print(' ');
            }
            //end of the row
            System.out.print('\n');
        }
    }
}